Git Rebase 与 Cherry-pick 实用指南
分支合并场景中经常需要整理提交历史:把若干提交压成一个、丢弃中间过程提交、或把某个提交单独应用到另一个分支。git rebase 和 git cherry-pick 是完成这些操作的核心命令。本文覆盖两者的常用姿势:交互式 rebase 的命令清单、少量与大量提交的压缩方案、cherry-pick 的范围语法,以及改写历史后的推送注意事项。
Git Rebase
基本用法
交互式 rebase 可以对一系列提交重新编排:
git rebase -i <起始commit> 编辑器中会列出起始 commit 之后的全部提交:
pick abc1234 c1 message
pick def5678 c2 message
pick ghi9012 c3 message 可用的操作命令:
| 命令 | 缩写 | 作用 |
|---|---|---|
pick | p | 保留该提交不变 |
squash | s | 将该提交压缩到前一个提交,合并 commit message |
fixup | f | 同 squash,但丢弃该提交的 commit message |
reword | r | 保留提交,但修改 commit message |
edit | e | 暂停在该提交,可修改内容后继续 |
drop | d | 丢弃该提交 |
典型场景:合并部分提交
假设 feat 分支上有 5 个提交(c1-c5),合并到 develop 前想把 c1、c2 压缩为一个提交:
git checkout feat
git rebase -i c1~1 c1~1 表示 c1 的父提交,rebase 的编辑列表从 c1 开始。在编辑器中将 c2 一行改为 squash:
pick c1 message
squash c2 message
pick c3 message
pick c4 message
pick c5 message 保存后编辑合并后的 commit message。完成后合并到 develop:
git checkout develop
git merge feat develop 上的提交历史变为 c1'、c3、c4、c5。
大量提交的压缩
当需要压缩的提交数量很多(如 100 个),逐行手改编辑器不现实,有两种替代方案。
方案一:soft reset + 重新提交
# 找到第 N+1 个提交的 hash
git log --oneline | head -n 101 | tail -n 1
# soft reset 到前 N 个提交之前,改动全部回到暂存区
git reset --soft <第N+1个提交的父提交>
# 重新提交压缩后的内容
git commit -m "合并前N个提交的信息"
# 把剩余提交 cherry-pick 回来
git cherry-pick <c_{N+1}>..<feat的HEAD> 方案二:用 sed 自动替换
通过 GIT_SEQUENCE_EDITOR 环境变量替换 rebase 的序列编辑器,用 sed 自动把指定范围的 pick 改为 squash:
# 将 todo 列表第 2 到第 100 行的 pick 替换为 squash
GIT_SEQUENCE_EDITOR="sed -i '2,100s/^pick/squash/'" git rebase -i <起始commit> GIT_SEQUENCE_EDITOR 只影响 rebase 生成的提交序列编辑,不改变其他 Git 行为。
切换默认编辑器
交互式 rebase 依赖文本编辑器,不想用默认编辑器时可以临时或永久指定:
# 全局设置为 vim
git config --global core.editor "vim"
# 临时使用 vim
GIT_EDITOR=vim git rebase -i ...
# 通过环境变量全局生效(写入 ~/.bashrc 或 ~/.zshrc)
export EDITOR=vim
export VISUAL=vim Git Cherry-pick
基本用法
将指定的提交应用到当前分支:
git cherry-pick <commit-hash> 批量操作
cherry-pick 支持范围语法,一次应用多个提交:
# 左开右闭:不含 c3,含 c5
git cherry-pick c3..c5
# 两端都包含:含 c3 和 c5
git cherry-pick c3^..c5 多个不连续的提交也可以一次指定:
git cherry-pick c3 c7 c10 范围语法 A..B 是左开右闭(不含 A,含 B);用 A^..B 可以两端都包含。
典型场景:压缩后重建提交
结合 soft reset 使用,先压缩一批提交,再把剩余提交按范围 cherry-pick 回来:
# 将前 100 个提交压缩为一个
git reset --soft <base-commit>
git commit -m "feat: 初始实现"
# 将剩余提交按范围 cherry-pick 回来
git cherry-pick c101^..c150 要点
- rebase 会改写提交历史。已完成 rebase 的分支如果已推送到远程,再次推送需要
git push --force-with-lease——它在远端被他人更新过时会拒绝推送,比裸git push -f安全 - 少量提交用交互式 rebase,大量提交用
git reset --soft或GIT_SEQUENCE_EDITOR+ sed - cherry-pick 范围
A..B是左开右闭,A^..B是两端包含 - rebase 前建议先创建备份分支:
git branch feat-backup feat,操作失误时可以从备份恢复